<?php
/**
 * 转换Array成XML内容
 * 1，设置数据节点属性，请使用<节点名字::属性名字>方式保存在节点数据数组中
 * 例如:
 *    "result" => array("result::name" => "aaa"）
 * 转换后的:
 * <result name="aaa"/>
 *
 * 2, 设置带属性和值的节点，需按以下格式组装数组数据
 * "节点名字" => array("_" => "节点值", "节点名::属性名" => "属性值")
 * 例如:
 * "aaa" => array('_' => "aaabb", "aaa::key" => 1, "aaa::key2" => 2)
 * 转换后将是:
 * <aaa key="1" key2="2">aaabb</result>
 *
 * 3, 其他彩蛋请自己探索
 *
 * @useage:
 * $xml_data = array(
 *          "result" => array(
 *              "result::name" => "aaa",
 *              "aaa"          => array('_' => "aaabb", "aaa::key" => 1, "aaa::key2" => 2),
 *              "bbb"          => array(
 *                                "aaa"=>array("1", "2", "3")
 *                                ),
 *              "lists" => array(1,2,3)
 *              )
 *          );
 *  /*
 *   * 转换xml
 *   * @param  array   $xml_data    Hash数据
 *   * @param  string  $list_flag   数组标记
 *   * @return string
 *  *\/
 *  np_build_xml::xml_header();
 *  echo np_build_xml::covert($xml_data, 'node');
 *
 * @author:  tom.chen <ziwei.chen@starcor.cn>
 * @date:    2016-09-02 14:08:40
 */

class np_build_xml {
    // 输出XML编码类型
    static public $charset = "ISO-8859-15";

    /*
     * 转换xml
     * @param  array   $xml_data    Hash数据
     * @param  string  $list_flag   数组标记
     * @return string
     */
    static public function covert($datas=array(), $list_flag='list')
    {
        $xml = new DOMDocument("1.0",  self::$charset);
        foreach ($datas as $node_name => $node_val) {
            self::create_node($node_name, $node_val, $list_flag, $xml);
        }
        return $xml->saveXML();
    }

    /**
     * 递归创建xml节点
     * @param  string $node_name   节点名/标签名
     * @param  void   $data        节点数据
     * @param  string $list_flag   数字节点标签名
     * @param  object &$xml        [引用]xml对象
     * @param  object &$parent     [引用]父节点对象
     * @param  string $parent_name 父节点名字
     * @return object              XML数据
     */
    static protected function create_node($node_name, $data, $list_flag,
                                         &$xml, &$parent=null, $parent_name=null)
    {
        // 如果是节点名称是数字，则使用数字节点的标签名字
        $node_name = is_numeric($node_name) ? $list_flag : $node_name;
        // 如果是节点属性，则添加属性，并直接返回
        if (!empty($parent) && self::is_attributes($node_name, $data, $parent_name, $parent))
        {
            return true;
        }

        // 根据节点数据类型，创建节点对象
        // #TODO 代码可进一步优化
        if (is_array($data))
        {
            if (array_key_exists('_', $data))
            {
                $xml_node = $xml->createElement($node_name, $data['_']);
                unset($data['_']);
            }
            else
            {
                $xml_node = $xml->createElement($node_name);
            }
            foreach ($data as $child_name => $child_val) {
                self::create_node($child_name, $child_val, $list_flag, $xml, $xml_node, $node_name);
            }
        }
        else
        {
            $xml_node = $xml->createElement($node_name, $data);
        }

        // 将节点数据添加到父节点上
        // ($parent|$xml)->appendChild($xml_node);
        if (!empty($parent))
        {
            $parent->appendChild( $xml_node );
        }
        else
        {
            $xml->appendChild( $xml_node );
        }
    }

    /**
     * 判断数据是否为属性，若是，则添加到该节点的属性
     * @param  string  $attr_name 属性名
     * @param  string  $attr_val  属性值
     * @param  string  $node_name 节点名
     * @param  object  &$node     [引用]节点数据
     * @return boolean
     */
    static protected function is_attributes($attr_name, $attr_val, $node_name, &$node)
    {
        // 如果节点数据为数组，则直接返回
        if (is_array($attr_val))
        {
            return false;
        }

        // 取节点属性名字
        $attr_names = explode('::', $attr_name, 2);
        if (count($attr_names) == 2 && !empty($attr_names[1]) && $attr_names[0] == $node_name)
        {
            $node->setAttribute($attr_names[1], $attr_val);
            return true;
        }

        return false;
    }

    /**
     * 设置响应页面头为xml
     */
    static public function xml_header()
    {
        header("content-type: application/xml; charset=ISO-8859-15");
    }
}
